package com.naza.rpc.circuitBreaker;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 
 * 熔断器配置
 *
 * @author yl
 */
public class CircuitBreakerConfig {
    private final int ringBufferSizeInClosedState;

    private final float failureRateThresholdInClosedState;

    private final long waitDurationInOpenState;

    private final boolean hasHalfOpenState;

    private final int ringBufferSizeInHalfOpenState;

    // 半打开失败率
    private final float failureRateThresholdInHalfOpenState;

    private final int maxConcurrentExecutionsInHalfOpenState;

    private List<Class> failureConditions = new ArrayList<Class>();

    private CircuitBreakerConfig(int ringBufferSizeInClosedState, float failureRateThresholdInClosedState,
            long waitDurationInOpenState, boolean hasHalfOpenState, int ringBufferSizeInHalfOpenState,
            float failureRateThresholdInHalfOpenState, int maxConcurrentExecutionsInHalfOpenState,
            List<Class> failureConditions) {
        this.failureRateThresholdInClosedState = failureRateThresholdInClosedState;
        this.failureRateThresholdInHalfOpenState = failureRateThresholdInHalfOpenState;
        this.waitDurationInOpenState = waitDurationInOpenState;
        this.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState;
        this.ringBufferSizeInClosedState = ringBufferSizeInClosedState;
        this.failureConditions = failureConditions;
        this.maxConcurrentExecutionsInHalfOpenState = maxConcurrentExecutionsInHalfOpenState;
        this.hasHalfOpenState = hasHalfOpenState;
    }

    @SuppressWarnings("unchecked")
    public boolean isFailure(Throwable failure) {
        if (failure == null)
            return false;
        if (failureConditions == null || failureConditions.isEmpty()) {
            return true;
        }
        for (Class clz : failureConditions) {
            if (clz.isAssignableFrom(failure.getClass())) {
                return true;
            }
        }
        // Return true if the failure is not checked
        return false;
    }

    public int getRingBufferSizeInClosedState() {
        return ringBufferSizeInClosedState;
    }

    public float getFailureRateThresholdInClosedState() {
        return failureRateThresholdInClosedState;
    }

    public long getWaitDurationInOpenState() {
        return waitDurationInOpenState;
    }

    public int getRingBufferSizeInHalfOpenState() {
        return ringBufferSizeInHalfOpenState;
    }

    public float getFailureRateThresholdInHalfOpenState() {
        return failureRateThresholdInHalfOpenState;
    }

    public List<Class> getFailureConditions() {
        return failureConditions;
    }

    public boolean hasHalfOpenState() {
        return hasHalfOpenState;
    }

    public int getMaxConcurrentExecutionsInHalfOpenState() {
        return maxConcurrentExecutionsInHalfOpenState;
    }

    /**
     * Returns a builder to create a custom CircuitBreakerConfig.
     *
     * @return a {@link CircuitBreakerConfig.Builder}
     */
    public static CircuitBreakerConfig.Builder custom() {
        return new Builder();
    }

    public static class Builder {
        private int failureRateThresholdInClosedStatus;

        private int ringBufferSizeInHalfOpenState;

        private boolean hasHalfOpenState;

        private int ringBufferSizeInClosedState;

        private long waitDurationInOpenState;

        private int failureRateThresholdInHalfOpenStatus;

        private int maxConcurrentExecutionsInHalfOpenState;

        // The default exception predicate counts all exceptions as failures.
        private List<Class> failureConditions = new ArrayList<Class>();

        /**
         * Configures the failure rate threshold in percentage above which the
         * CircuitBreaker should trip open and start short-circuiting calls.
         * <p/>
         * The threshold must be between 1 and 100. Default value is 50
         * percentage.
         *
         * @param failureRateThreshold
         *            the failure rate threshold in percentage
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder failureRateThresholdInClosedStatus(int failureRateThreshold) {
            if (failureRateThreshold < 1 || failureRateThreshold > 100) {
                throw new IllegalArgumentException(
                        "failureRateThresholdInClosedStatus must be between 1 and 100");
            }
            this.failureRateThresholdInClosedStatus = failureRateThreshold;
            return this;
        }

        public Builder hasHalfOpenState(boolean hasHalfOpenState) {
            this.hasHalfOpenState = hasHalfOpenState;
            return this;
        }

        public Builder maxConcurrentExecutionsInHalfOpenState(int max) {
            if (max < 1) {
                throw new IllegalArgumentException(
                        "maxConcurrentExecutionsInHalfOpenState must be greater than 0");
            }
            this.maxConcurrentExecutionsInHalfOpenState = max;
            return this;
        }

        /**
         * Configures the failure rate threshold in percentage above which the
         * CircuitBreaker should trip open and start short-circuiting calls.
         * <p/>
         * The threshold must be between 1 and 100. Default value is 50
         * percentage.
         *
         * @param failureRateThreshold
         *            the failure rate threshold in percentage
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder failureRateThresholdInHalfOpenStatus(int failureRateThreshold) {
            if (failureRateThreshold < 1 || failureRateThreshold > 100) {
                throw new IllegalArgumentException(
                        "failureRateThresholdInHalfOpenStatus must be between 1 and 100");
            }
            this.failureRateThresholdInHalfOpenStatus = failureRateThreshold;
            return this;
        }

        /**
         * Configures the wait duration which specifies how long the
         * CircuitBreaker should stay open, before it switches to half open.
         * Default value is 60 seconds.
         *
         * @param waitDurationInOpenState
         *            the wait duration(ms) which specifies how long the
         *            CircuitBreaker should stay open
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder waitDurationInOpenState(long waitDurationInOpenState) {
            this.waitDurationInOpenState = waitDurationInOpenState;
            return this;
        }

        /**
         * Configures the size of the ring buffer when the CircuitBreaker is
         * half open. The CircuitBreaker stores the success/failure success /
         * failure status of the latest calls in a ring buffer. For example, if
         * {@code ringBufferSizeInClosedState} is 10, then at least 10 calls
         * must be evaluated, before the failure rate can be calculated. If only
         * 9 calls have been evaluated the CircuitBreaker will not trip back to
         * closed or open even if all 9 calls have failed.
         * <p/>
         * The size must be greater than 0. Default size is 10.
         *
         * @param ringBufferSizeInHalfOpenState
         *            the size of the ring buffer when the CircuitBreaker is is
         *            half open
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder ringBufferSizeInHalfOpenState(int ringBufferSizeInHalfOpenState) {
            if (ringBufferSizeInHalfOpenState < 1) {
                throw new IllegalArgumentException("ringBufferSizeInHalfOpenState must be greater than 0");
            }
            this.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState;
            return this;
        }

        /**
         * Configures the size of the ring buffer when the CircuitBreaker is
         * closed. The CircuitBreaker stores the success/failure success /
         * failure status of the latest calls in a ring buffer. For example, if
         * {@code ringBufferSizeInClosedState} is 100, then at least 100 calls
         * must be evaluated, before the failure rate can be calculated. If only
         * 99 calls have been evaluated the CircuitBreaker will not trip open
         * even if all 99 calls have failed.
         * <p/>
         * The size must be greater than 0. Default size is 100.
         *
         * @param ringBufferSizeInClosedState
         *            the size of the ring buffer when the CircuitBreaker is
         *            closed.
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder ringBufferSizeInClosedState(int ringBufferSizeInClosedState) {
            if (ringBufferSizeInClosedState < 1) {
                throw new IllegalArgumentException("ringBufferSizeInClosedState must be greater than 0");
            }
            this.ringBufferSizeInClosedState = ringBufferSizeInClosedState;
            return this;
        }

        /**
         * Configures a Predicate which evaluates if an exception should be
         * recorded as a failure and thus increase the failure rate. The
         * Predicate must return true if the exception should count as a
         * failure, otherwise it must return false.
         *
         * @param failureConditions
         *            the Class type which evaluates if an exception should be
         *            recorded as a failure and thus trigger the CircuitBreaker
         * @return the CircuitBreakerConfig.Builder
         */
        public Builder failOn(Class[] failureConditions) {
            if (failureConditions != null) {
                Collections.addAll(this.failureConditions, failureConditions);
            }
            return this;
        }

        /**
         * Builds a CircuitBreakerConfig
         *
         * @return the CircuitBreakerConfig
         */
        public CircuitBreakerConfig build() {
            return new CircuitBreakerConfig(ringBufferSizeInClosedState, failureRateThresholdInClosedStatus,
                    waitDurationInOpenState, hasHalfOpenState, ringBufferSizeInHalfOpenState,
                    failureRateThresholdInHalfOpenStatus, maxConcurrentExecutionsInHalfOpenState,
                    failureConditions);
        }
    }
}
